﻿using System.Collections.Generic;
using System.Linq;
using Notoric.Commanding.CommandBase;
using Notoric.ViewModel.Elements.CWNotation;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for splitting a group of beamed symbols.
    /// </summary>
    public class SplitBeamGroupCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SplitBeamGroupCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The command stack where the command will be put after its execution</param>
        /// <param name="redoStack">The command stack where the command will be put when it has been undone</param>
        public SplitBeamGroupCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<IList<DurationalSymbol>>("Symbols", ParameterDirection.In);
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var beams = (from symbol in Symbols
                         select symbol.GroupBeam
                         ).Distinct();
            beam = beams.Single();
            var modelBeam = (Model.Elements.CWNotation.Beam)beam.AdaptedElement;

            var prevBeamSymbols = new List<DurationalSymbol>(beam.BeamedSymbols.TakeWhile(s => s != Symbols[0]));
            var nextBeamSymbols = new List<DurationalSymbol>(beam.BeamedSymbols.Skip(prevBeamSymbols.Count() + Symbols.Count));

            for (int i = prevBeamSymbols.Count - 1; i >= 0; --i)
            {
                if (!(prevBeamSymbols[i] is Chord))
                    prevBeamSymbols.RemoveAt(i);
                else
                    break;
            }

            while (nextBeamSymbols.Count > 0 && !(nextBeamSymbols[0] is Chord))
                nextBeamSymbols.RemoveAt(0);

            prevSymbols = new List<Model.Elements.CWNotation.DurationalSymbol>(modelBeam.BeamedSymbols);
            Model.Elements.CWNotation.Beam.SplitGroup(prevSymbols);

            modelBeam.FindOwner<Model.Elements.CWNotation.Staff>().Attachments.Remove(modelBeam);

            if (prevBeamSymbols.Count > 1)
            {
                prevBeamCommand = new MakeBeamGroupCommand(UndoStack, RedoStack)
                {
                    Symbols = prevBeamSymbols,
                    UseStacks = false
                };
                prevBeamCommand.Execute(null);
            }
            if (nextBeamSymbols.Count > 1)
            {
                nextBeamCommand = new MakeBeamGroupCommand(UndoStack, RedoStack)
                {
                    Symbols = nextBeamSymbols,
                    UseStacks = false
                };
                nextBeamCommand.Execute(null);
            }

            beam.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelBeam = (Model.Elements.CWNotation.Beam)beam.AdaptedElement;
            Model.Elements.CWNotation.Beam.SplitGroup(prevSymbols);
            modelBeam.FindOwner<Model.Elements.CWNotation.Staff>().Attachments.Remove(modelBeam);

            if (prevBeamCommand != null)
                prevBeamCommand.Execute(null);
            if (nextBeamCommand != null)
                nextBeamCommand.Execute(null);

            beam.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            if (prevBeamCommand != null)
                prevBeamCommand.Execute(null);
            if (nextBeamCommand != null)
                nextBeamCommand.Execute(null);

            var modelBeam = (Model.Elements.CWNotation.Beam)beam.AdaptedElement;
            modelBeam.RestoreBeam(prevSymbols);

            modelBeam.FindOwner<Model.Elements.CWNotation.Staff>().Attachments.Add(modelBeam);
            var staff = beam.FindOwner<Staff>();
            var vmBeam = staff.Attachments.First(a => a.AdaptedElement == modelBeam);
            var index = staff.Attachments.IndexOf(vmBeam);
            staff.Attachments[index] = beam;

            beam.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the symbols.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="IList{T}"/> of <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The symbols.<br/>
        /// This property wraps the "Symbols" command parameter.
        /// </para>
        /// </value>
        public IList<DurationalSymbol> Symbols
        {
            get { return GetParameterValue<IList<DurationalSymbol>>("Symbols"); }
            set { SetParameterValue<IList<DurationalSymbol>>("Symbols", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the beam manipulated by this command.
        /// </summary>
        private Beam beam;
        /// <summary>
        /// Holds the command that has created the beam that followed the split part.
        /// </summary>
        private UndoableCommandBase nextBeamCommand;
        /// <summary>
        /// Holds the command that has created the beam that preceeded the split part.
        /// </summary>
        private UndoableCommandBase prevBeamCommand;
        /// <summary>
        /// Holds the list of original beamed symbols collection.
        /// </summary>
        private List<Model.Elements.CWNotation.DurationalSymbol> prevSymbols;

        #endregion
    }
}
